logo头像
Snippet 博客主题

React Native 自定义控件专题

本文于303天之前发表,文中内容可能已经过时。

React Native通过近两年的迭代和维护,最新版本已经到了0.45.1,关于最新版本的介绍请查看我之前的博客:0.45新特性。话说回来,尽管迭代的挺快,但还是有很多坑,很多基础的组件和API还是不完善。

今天给大家带来的自定义小专题,其实对于React Native来说,自定义组件的过程更像是Android、iOS的组合控件。大体步骤有如下几个步骤(不完全准确,但是方向大体准确):
1,定义构造函数constructor;
2,定义组件属性propTypes;
3,绘制界面;
4,添加更新界面逻辑等

自定义Toast

在系统组件中,RN为我们提供了ToastAndroid组件,但是对于iOS好像并没有直接提供,这时候我们就想到了自定义控件了。如下图所示:
这里写图片描述

我们之前讲过Animated组件,这个组件可以实现渐变,缩放,旋转等动画效果,在这里,我们可以用它来实现Toast的功能。比如,显示两秒后消失,为了对显示的位置进行设置,我们还可以设置显示的位置,所以绘制render的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
render() {
let top;
switch (this.props.position){
case 'top':
top=160;
break;
case 'center':
top=height /2;
break;
case 'bottom':
top=height - 160;
break;
}
let view = this.state.isShow ?
<View
style={[styles.container,{top:top}]}
pointerEvents="none"
>
<Animated.View
style={[styles.content,{opacity:this.state.opacityValue}]}
>
<Text style={styles.text}>{this.state.text}</Text>
</Animated.View>
</View> : null;
return view;
}

显示时长控制方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
show(text, duration) {
if(duration>=DURATION.LENGTH_LONG){
this.duration=DURATION.LENGTH_LONG;
}else {
this.duration=DURATION.LENGTH_SHORT;
}
this.setState({
isShow: true,
text: text,
});
this.isShow=true;
this.state.opacityValue.setValue(OPACITY)
this.close();
}

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component,PropTypes} from 'react';
import {
StyleSheet,
View,
Animated,
Dimensions,
Text,
} from 'react-native'
export const DURATION = {LENGTH_LONG: 2000, LENGTH_SHORT: 500};
const {height, width} = Dimensions.get('window');
const OPACITY=0.6;
const dismissKeyboard = require('dismissKeyboard')
export default class ToastUtil extends Component {
static propTypes = {
position: PropTypes.oneOf([
'top',
'center',
'bottom',
]),
}
static defaultProps = {
position:'center',
}
constructor(props) {
super(props);
this.state = {
isShow: false,
text: '',
opacityValue:new Animated.Value(OPACITY),
}
}
show(text, duration) {
if(duration>=DURATION.LENGTH_LONG){
this.duration=DURATION.LENGTH_LONG;
}else {
this.duration=DURATION.LENGTH_SHORT;
}
this.setState({
isShow: true,
text: text,
});
this.isShow=true;
this.state.opacityValue.setValue(OPACITY)
this.close();
}
close() {
if(!this.isShow)return;
this.timer && clearTimeout(this.timer);
this.timer = setTimeout(() => {
Animated.timing(
this.state.opacityValue,
{
toValue: 0.0,
duration:1000,
}
).start(()=>{
this.setState({
isShow: false,
});
this.isShow=false;
});
}, this.duration);
}
componentWillUnmount() {
this.timer && clearTimeout(this.timer);
}
render() {
let top;
switch (this.props.position){
case 'top':
top=160;
break;
case 'center':
top=height /2;
break;
case 'bottom':
top=height - 160;
break;
}
let view = this.state.isShow ?
<View
style={[styles.container,{top:top}]}
pointerEvents="none"
>
<Animated.View
style={[styles.content,{opacity:this.state.opacityValue}]}
>
<Text style={styles.text}>{this.state.text}</Text>
</Animated.View>
</View> : null;
return view;
}
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 0,
right: 0,
alignItems: 'center',
},
content: {
backgroundColor: 'black',
opacity: OPACITY,
borderRadius: 5,
padding: 10,
},
text:{
color:'white'
},
})

如何使用:

1
2
3
4
5
6
7
<Toast ref="toast"/>
//省略...
<Text style={styles.styleText} onPress={()=>{
this.refs.toast.show('你点击了忘记密码!',3000);}}>
忘记密码?
</Text>
//省略...

获取验证码

在很多应用开发中都会涉及到获取手机验证码的场景,例如登录或者注册获取验证码。如下图:
这里写图片描述这里写图片描述

那么按照自定义组件的流程,先添加构造函数,并定义必须的一些字段(相关属性),并完成初始化:

1
2
3
4
5
6
7
8
static propTypes = {
style: PropTypes.object,//style属性
textStyle: Text.propTypes.style,//文本文字
onClick: PropTypes.func,//点击事件
disableColor: PropTypes.string,//倒计时过程中颜色
timerTitle: PropTypes.string,//倒计时文本
enable: React.PropTypes.oneOfType([React.PropTypes.bool,React.PropTypes.number])
};

2,构造函数:

1
2
3
4
5
6
7
8
9
10
11
constructor(props) {
super(props)
this.state = {
timerCount: this.props.timerCount || 60,//默认倒计时时间
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true,
};
this.shouldStartCountting = this.shouldStartCountting.bind(this)
this.countDownAction = this.countDownAction.bind(this)
}

3,添加绘制界面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
render() {
const {onClick, style, textStyle, disableColor} = this.props;
const {counting, timerTitle, selfEnable} = this.state;
return (
<TouchableOpacity activeOpacity={counting ? 1 : 0.8} onPress={() => {
if (!counting &&selfEnable) {
this.setState({selfEnable: false});
this.shouldStartCountting(true);
};
}}>
<View
style={styles.styleCodeView}>
<Text
style={[{fontSize: 12}, textStyle, {color: ((!counting && selfEnable) ? textStyle.color : disableColor || 'gray')}]}>{timerTitle}</Text>
</View>
</TouchableOpacity>
)
}

4,添加逻辑代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
shouldStartCountting(shouldStart) {
if (this.state.counting) {
return
}
if (shouldStart) {
this.countDownAction()
this.setState({counting: true, selfEnable: false})
} else {
this.setState({selfEnable: true})
}
}
//倒计时逻辑
countDownAction() {
const codeTime = this.state.timerCount;
this.interval = setInterval(() => {
const timer = this.state.timerCount - 1
if (timer === 0) {
this.interval && clearInterval(this.interval);
this.setState({
timerCount: codeTime,
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true
})
} else {
this.setState({
timerCount: timer,
timerTitle: `重新获取(${timer}s)`,
})
}
}, 1000)
}

说明:
shouldStartCountting:回调函数,接受一个Bool类型的参数
1,shouldStartCountting(true),开始倒计时,倒计时结束时自动恢复初始状态
2,shouldStartCountting(false), 按钮的selfEnable会立即被置为true
所以,获取验证码的完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component,PropTypes} from 'react';
import {
Text,
StyleSheet,
View,
TouchableOpacity,
} from 'react-native';
var Dimensions = require('Dimensions');
var screenWidth = Dimensions.get('window').width;
export default class TimerButton extends Component {
constructor(props) {
super(props)
this.state = {
timerCount: this.props.timerCount || 60,
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true,
};
this.shouldStartCountting = this.shouldStartCountting.bind(this)
this.countDownAction = this.countDownAction.bind(this)
}
static propTypes = {
style: PropTypes.object,
textStyle: Text.propTypes.style,
onClick: PropTypes.func,
disableColor: PropTypes.string,
timerTitle: PropTypes.string,
enable: React.PropTypes.oneOfType([React.PropTypes.bool,React.PropTypes.number])
};
countDownAction() {
const codeTime = this.state.timerCount;
this.interval = setInterval(() => {
const timer = this.state.timerCount - 1
if (timer === 0) {
this.interval && clearInterval(this.interval);
this.setState({
timerCount: codeTime,
timerTitle: this.props.timerTitle || '获取验证码',
counting: false,
selfEnable: true
})
} else {
this.setState({
timerCount: timer,
timerTitle: `重新获取(${timer}s)`,
})
}
}, 1000)
}
shouldStartCountting(shouldStart) {
if (this.state.counting) {
return
}
if (shouldStart) {
this.countDownAction()
this.setState({counting: true, selfEnable: false})
} else {
this.setState({selfEnable: true})
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
const {onClick, style, textStyle, disableColor} = this.props;
const {counting, timerTitle, selfEnable} = this.state;
return (
<TouchableOpacity activeOpacity={counting ? 1 : 0.8} onPress={() => {
if (!counting &&selfEnable) {
this.setState({selfEnable: false});
this.shouldStartCountting(true);
};
}}>
<View
style={styles.styleCodeView}>
<Text
style={[{fontSize: 12}, textStyle, {color: ((!counting && selfEnable) ? textStyle.color : disableColor || 'gray')}]}>{timerTitle}</Text>
</View>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20
},
styleCodeView: {
height: 28,
width: screenWidth*0.22,
borderColor: '#dc1466',
borderWidth: 1,
borderRadius: 5,
justifyContent: 'center',
alignItems: 'center',
},
styleTextCode: {
fontSize: 12,
color: '#dc1466',
textAlign: 'center',
},
});

如何使用?

1
2
3
4
5
6
7
8
9
10
11
12
import TimerButton from './TimerButton'
var Dimensions = require('Dimensions');
var screenWidth = Dimensions.get('window').width;
//省略...
<TimerButton
style={{width: screenWidth*0.2,marginRight: 10}}
timerCount={60}
textStyle={{color: '#dc1466'}}
onclick={(start)=>{
}}/>
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者

上一篇